package com.easychat.websocket;

import com.easychat.dto.MessageSendDto;
import com.easychat.dto.WsInitData;
import com.easychat.entity.ChatMessage;
import com.easychat.entity.ChatSession;
import com.easychat.entity.ChatSessionUser;
import com.easychat.entity.UserInfo;
import com.easychat.entity.constants.Constants;
import com.easychat.entity.query.ChatMessageQuery;
import com.easychat.entity.query.ChatSessionUserQuery;
import com.easychat.entity.query.UserContactApplyQuery;
import com.easychat.enums.MessageTypeEnum;
import com.easychat.enums.UserContactApplyStatusEnum;
import com.easychat.enums.UserContactTypeEnum;
import com.easychat.mappers.ChatMessageDao;
import com.easychat.mappers.ChatSessionUserDao;
import com.easychat.mappers.UserContactApplyDao;
import com.easychat.mappers.UserInfoDao;
import com.easychat.redis.RedisComponent;
import com.easychat.service.ChatSessionUserService;
import com.easychat.utils.JsonUtils;
import com.easychat.utils.StringTools;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * ws的channel的工具类
 * @author 10 2024/10/18
 */
@Component
@Slf4j
public class ChannelContextUtils {
    private static final ConcurrentHashMap<String,Channel> USER_CONTEXT_MAP = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, ChannelGroup> GROUP_CONTEXT_MAP = new ConcurrentHashMap<>();
    @Resource
    private RedisComponent redisComponent;
    @Resource
    private UserInfoDao userInfoDao;
    @Resource
    private ChatSessionUserDao chatSessionUserDao;
    @Resource
    @Lazy
    private ChatSessionUserService chatSessionUserService;
    @Resource
    private ChatMessageDao chatMessageDao;
    @Resource
    private UserContactApplyDao userContactApplyDao;

    /**
     * 连接后在内存绑定userId 和channel，放到map里 方便后面获取
     * @param userId
     * @param channel
     */
    public void addContext(String userId, Channel channel){
        String channelId = channel.id().toString();
        AttributeKey attributeKey = null;
        if(!AttributeKey.exists(channelId)){
            attributeKey = AttributeKey.newInstance(channelId);
        }else {
            attributeKey = AttributeKey.valueOf(channelId);
        }
        channel.attr(attributeKey).set(userId);

        List<String> contactIdList = redisComponent.getUserContactList(userId);
        for(String groupId : contactIdList){
            if(groupId.startsWith(UserContactTypeEnum.GROUP.getPrefix())){
                add2Group(groupId,channel);
            }
        }
        USER_CONTEXT_MAP.put(userId,channel);
        redisComponent.saveUserHeartBeat(userId);

        //更新用户最后连接时间
        UserInfo updateInfo = new UserInfo();
        updateInfo.setLastLoginTime(new Date());
        userInfoDao.updateByUserId(updateInfo,userId);

        //给用户发送消息
        UserInfo userInfo = userInfoDao.queryById(userId);
        Long sourceLastOffTime = userInfo.getLastOffTime();
        Long lastOffTime = sourceLastOffTime;
        //只寻找三天以内的信息
        if(sourceLastOffTime != null && System.currentTimeMillis() - Constants.MILLIS_SECONDS_3DAY_AGO >  sourceLastOffTime){
                lastOffTime = System.currentTimeMillis() - Constants.MILLIS_SECONDS_3DAY_AGO;
        }
        /**
         * 1、查询会话信息 查询用户所有的会话信息 ，保证换了设备会话同步
         */
        ChatSessionUser sessionUser = new ChatSessionUser();
        ChatSessionUserQuery sessionUserQuery = new ChatSessionUserQuery();
        sessionUserQuery.setUserId(userId);
        sessionUserQuery.setOrderBy("last_receive_time desc");
        sessionUser.setUserId(userId);
//        List<ChatSessionUser> chatSessionUserList = chatSessionUserDao.selectList(sessionUser);
        List<ChatSessionUser> chatSessionUserList = chatSessionUserService.findListByParam(sessionUserQuery);

        WsInitData wsInitData = new WsInitData();
        wsInitData.setChatSessionList(chatSessionUserList);
        /**
         * 2、查询聊天消息
         */
        //查询所以的联系群组 （不管有没有消息，用户加入的群组都要查）
        List<String> groupList = contactIdList.stream().filter(item -> item.startsWith(UserContactTypeEnum.GROUP.getPrefix())).collect(Collectors.toList());
        //加上用户自己 （只需要查询别人给自己发的消息）
        groupList.add(userId);
        ChatMessageQuery chatMessageQuery = new ChatMessageQuery();
        chatMessageQuery.setContactIdList(groupList);
        chatMessageQuery.setLastReceiveTime(lastOffTime);
        List<ChatMessage> chatMessageList = chatMessageDao.selectList(chatMessageQuery);
        wsInitData.setChatMessageList(chatMessageList);

        /**
         * 3、查询好友申请
         */
        UserContactApplyQuery applyQuery = new UserContactApplyQuery();
        applyQuery.setReceiveUserId(userId);
        applyQuery.setLastApplyTimestamp(lastOffTime);
        applyQuery.setStatus(UserContactApplyStatusEnum.INIT.getStatus());
        Integer applyCount = userContactApplyDao.selectCount(applyQuery);
        wsInitData.setApplyCount(applyCount);

        //发送消息
        MessageSendDto messageSendDto = new MessageSendDto<>();
        messageSendDto.setMessageType(MessageTypeEnum.INIT.getType());
        messageSendDto.setContactId(userId);
        messageSendDto.setExtendData(wsInitData);



    }

    //发送消息
    public  static void sendMsg(MessageSendDto messageSendDto,String receiveId){
        Channel sendChannel = USER_CONTEXT_MAP.get(receiveId);
        if(sendChannel == null){
            return;
        }
            //相对于客户端而言，联系人就是发送人，所以这里转一下再发送
        //因为发送过去是给客户端用户，这样就会认为联系人就是发送人
        if(MessageTypeEnum.ADD_FRIEND_SELF.getType().equals(messageSendDto.getMessageType())){
            UserInfo userInfo = (UserInfo) messageSendDto.getExtendData();
            messageSendDto.setMessageType(MessageTypeEnum.ADD_FRIEND.getType());
            messageSendDto.setContactId(userInfo.getUserId());
            messageSendDto.setContactName(userInfo.getNickName());
            messageSendDto.setExtendData(null);

        }else {
            messageSendDto.setContactId(messageSendDto.getSendUserId());
            messageSendDto.setContactName(messageSendDto.getSendUserNickName());
        }
        sendChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.convertObj2Json(messageSendDto)));

    }

    /**
     * 添加channel到群组channel
     * @param groupId
     * @param channel
     */
    public void add2Group(String groupId,Channel channel){
        ChannelGroup group = GROUP_CONTEXT_MAP.get(groupId);
        if(group==null){
            group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            GROUP_CONTEXT_MAP.put(groupId,group);
        }
        if(channel == null){
            return;
        }
        group.add(channel);
    }
    //向群组里发消息
    public void send2Group(String message){
        ChannelGroup group = GROUP_CONTEXT_MAP.get("10000");
        group.writeAndFlush(new TextWebSocketFrame(message));

    }
    public void removeContext(Channel channel){
        Attribute<String> attribute = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attribute.get();
        if(!StringTools.isEmpty(userId)){
            USER_CONTEXT_MAP.remove(userId);
        }
        redisComponent.removeUserHeartBeat(userId);
        //更新用户最后离线时间
        UserInfo userInfo = new UserInfo();
        userInfo.setLastOffTime(System.currentTimeMillis());
        userInfoDao.updateByUserId(userInfo,userId);
    }
    public void sendMessage(MessageSendDto messageSendDto){
        UserContactTypeEnum contactTypeEnum = UserContactTypeEnum.getByPrefix(messageSendDto.getContactId());
        switch (contactTypeEnum){
            case USER:
                send2User(messageSendDto);
                break;
            case GROUP:
                send2Group(messageSendDto);
                break;
        }

    }

    /**
     * 发送给单个用户的方法
     * @param messageSendDto
     */
    private void send2User(MessageSendDto messageSendDto){
        String contactId = messageSendDto.getContactId();
        if(StringTools.isEmpty(contactId)){
            return;
        }
        sendMsg(messageSendDto,contactId);
        //强制下线
        if(MessageTypeEnum.FORCE_OFF_LINE.getType().equals(messageSendDto.getMessageType())){
            //关闭通道
            closeContext(contactId);
        }
    }
    public void closeContext(String userId){
        if(StringTools.isEmpty(userId)){
            return;
        }
        redisComponent.cleanUserTokenByUserId(userId);
        Channel channel = USER_CONTEXT_MAP.get(userId);
        if(channel == null){
            return;
        }
        channel.close();
    }

    /**
     * 发送给群组
     * @param messageSendDto
     */
    private void send2Group(MessageSendDto messageSendDto){
        String contactId = messageSendDto.getContactId();
        if (StringTools.isEmpty(contactId)) {
            return;
        }
        if(!GROUP_CONTEXT_MAP.containsKey(contactId)){
            return;
        }
        messageSendDto.setContactId(messageSendDto.getSendUserId());
        messageSendDto.setContactName(messageSendDto.getSendUserNickName());
        ChannelGroup channels = GROUP_CONTEXT_MAP.get(contactId);
        channels.writeAndFlush(new TextWebSocketFrame(JsonUtils.convertObj2Json(messageSendDto)));

        //移出群聊
        MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByType(messageSendDto.getMessageType());
        if(MessageTypeEnum.LEAVE_GROUP==messageTypeEnum || MessageTypeEnum.REMOVE_GROUP==messageTypeEnum){
            String userId = (String) messageSendDto.getExtendData();
            redisComponent.removeUserContact(userId,contactId);
            Channel channel = USER_CONTEXT_MAP.get(userId);
            if(channel==null){
                return;
            }
            channels.remove(channel);
        }
        if(MessageTypeEnum.DISSOLUTION_GROUP==messageTypeEnum){
            GROUP_CONTEXT_MAP.remove(messageSendDto.getContactId());
            channels.close();
        }
    }

    public void addUser2Group(String groupOwnerId, String groupId) {
        ChannelGroup channels = GROUP_CONTEXT_MAP.get(groupId);
        if(channels==null){
            channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        }
        Channel groupOwnChannel = USER_CONTEXT_MAP.get(groupOwnerId);
        channels.add(groupOwnChannel);
        GROUP_CONTEXT_MAP.put(groupId,channels);
    }
}
